/* proj3_starter_app - ECE 544 Project #3 using Xilinx Kernel
 *
 * Copyright (c) 2012, 2013, 2014, Portland State University by Roy Kravitz.  All rights reserved.
 *
 * Author:		Sanket , Sanket
 * Date:		25th May 2014
 * Revision:	3.0
 *
 * Revision History:
 * -----------------
 * 	21-Feb-2012		RK		Created this program from the xilkernel sample application produced by SDK
 * 	16-May-2013		RK		Updated the program for Nexys3 board and revised homework 2 assignment
 *	12-May-2014		RK		Minor changes to ease support for both Nexys 3 and Nexys 4.  No functional changes
 *  25-May-2014	   SB,SG	added functionality to threads and support for WDT
 *
 * 	Description:
 * 	------------
 * 	This program implements ECE 544 Project #3.  It is a Xilinx-based design that is meant to be a learning tool
 * 	for incorporating several of the key functions provided by Xilkernel.   The application sets up 4 independent
 * 	threads.  Thread 1 periodically reads the switches on the FPGA developmengt board and sends an update to the LED thread.
 * 	Thread 2 responds to pushbutton press interrupts and sends an update to the LED thread whenever the state of
 * 	any of the pushbuttons on the FPGA development board change.   Thread 3 writes the new pushbutton and switch state to the
 * 	LEDs.   The three threads communicate via a message queue.  Thread 1 communicates to its interrupt handler through
 * 	the use of a semaphore.  The application also makes use of the Xiline watchdog timer.
 * 	The 4th thread (the master thread) sets up the Microblaze, initializes the peripherals, creates the other threads and
 * 	the semaphore and then enters a main loop where it wakes up periodically to reset the WDT.  Status messages are sent
 * 	via STDOUT through the USB serial port which can be connected to a PC running a terminal emulator such as putty.
 *
 * SW[3:0] of nexys3 are used for continous display on LEDs along with push buttons.
 * Center Push button is used for system reset.
 * SW[7] is used for causing forced crash of the system
 * Data is transmitted on serial port.
 *
 */

// Includes
#include "xmk.h"
#include "os_config.h"
#include "config/config_param.h"
#include "sys/ksched.h"
#include "sys/init.h"
#include <pthread.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <semaphore.h>
#include <sys/intr.h>
#include <sys/timer.h>
#include <sys/types.h>
#include <stdio.h>
#include <errno.h>

#include "xparameters.h"
#include "platform_config.h"
#include "platform.h"
#include "stdbool.h"
#include "xgpio.h"
#include "xwdttb.h"
#include "xtmrctr.h"
#include "xstatus.h"


// Declarations devices ID
#define BTN_GPIO_DEVICEID		XPAR_PUSH_BUTTONS_4BITS_DEVICE_ID
#define SW_GPIO_DEVICEID		XPAR_DIP_SWITCHES_8BITS_DEVICE_ID
#define LED_GPIO_DEVICEID		XPAR_LEDS_8BITS_DEVICE_ID
#define INTC_DEVICEID			XPAR_XPS_INTC_0_DEVICE_ID
#define WDT_DEVICEID			XPAR_XPS_TIMEBASE_WDT_0_DEVICE_ID
#define TMRCTR0_DEVICEID		XPAR_XPS_TIMER_0_DEVICE_ID
#define TMRCTR1_DEVICEID		XPAR_XPS_TIMER_1_DEVICE_ID

#define TMRCTR0_INTR_NUM		XPAR_XPS_INTC_0_XPS_TIMER_0_INTERRUPT_INTR
#define TMRCTR1_INTR_NUM		XPAR_XPS_INTC_0_XPS_TIMER_1_INTERRUPT_INTR
#define BTN_GPIO_INTR_NUM		XPAR_XPS_INTC_0_PUSH_BUTTONS_4BITS_IP2INTC_IRPT_INTR
#define WDT_INTR_NUM			XPAR_XPS_INTC_0_XPS_TIMEBASE_WDT_0_WDT_INTERRUPT_INTR

// some constants for function
#define BUTTON 1
#define SLIDE  2
#define TRUE   1
#define FALSE  0
#define mask_all_but_force_crash 0x80

// macro functions
#define MIN(a, b)  				( ((a) <= (b)) ? (a) : (b) )
#define MAX(a, b)  				( ((a) >= (b)) ? (a) : (b) )

// Peripheral instances
XGpio BTNInst, SWInst, LEDInst;
XTmrCtr TMRCTR1Inst;
XWdtTb WDTInst;

// Message passing variables
typedef struct					// LED message definition
{
	int msg_src;
	int msg_value;
} t_LED_message, *t_LED_messageptr;

const int led_msg_key = 1;		// message key for LED message queue
struct msqid_ds	led_msgstats;	// statistics from message queue
int led_msg_id;		// message queue id
t_LED_message led_message;
t_LED_message receive_msg;
t_LED_messageptr led_message_ptr = &led_message,receive_msg_ptr = &receive_msg;


// Synchronization variables
sem_t btn_press_sema;			// semaphore between clock tick ISR and the clock main thread
volatile u32 btn_state,sw_state;			// button state - shared between button handler and button thread
char force_crash = 0,but_mod = 0;

// Function declarations
void* master_thread(void *arg);
void* button_thread(void *arg);
void* switches_thread(void *arg);
void* leds_thread(void *arg);
void  button_handler(void);
void  wdt_handler(void);
XStatus init_peripherals(void);

int main()
{
    XStatus sts;
    //xil_printf("in main");
    // initialize the platform and the peripherals
    init_platform();
    sts = init_peripherals();
    if (sts != XST_SUCCESS)
    {
    	xil_printf("FATAL ERROR: Could not initialize the peripherals\n\r");
		xil_printf("Please power cycle or reset the system\n\r");
		return -1;
    }
    
	// check if WDT expired and caused the reset - if so, don't start
	if (XWdtTb_IsWdtExpired(&WDTInst))
	{
	 	// it's true, the WDT expired.
		// Clean the platform to start with new initialization
		cleanup_platform();
		xil_printf("\n*******************************************************************************\r\n");
		xil_printf("\rSystem Crashed !!!! \n\r\nRebooting System........\r");
		xil_printf("\n*******************************************************************************\r\n");
	}

    // Initialize xilkernel
    xilkernel_init();

    // Create the master thread
    xmk_add_static_thread(master_thread, 0);
    
    // Start the kernel
    xilkernel_start();
    
    // Should never be reached
    cleanup_platform();

    return 0;
}


// The master thread
void* master_thread(void *arg)
{
    pthread_t button;
    pthread_t switches;
    pthread_t leds;


    pthread_attr_t attr;
    struct sched_param spar;

    int ret;

    xil_printf("------------------------------------------------------------------------------\r\n");
    xil_printf("ECE 544 Project 3 using Xilinx Kernel By Sanket and Sameer\r\n");
    xil_printf("------------------------------------------------------------------------------\r\n");
    xil_printf("This Xilkernel based application reads the buttons and switches on the FPGA \r\n"
               "development board and displays them on the LEDs.  Even though the application\r\n"
               "is simple it uses sever of the synchronization and interprocess communication\r\n"
               "capabilities offered in the Xilkernel\r\n\r\n"
               "To demonstrate, press any of the buttons and/or flip switches on the board.\r\n"
               "The current state of the buttons and switches should be displayed on the LEDs\r\n");
    xil_printf("------------------------------------------------------------------------------\r\n\r\n\r\n");;

    xil_printf("MASTER: Master Thread Starting\r\n");

    // set the priority of all but the master thread to 1.  The master thread runs at priority 0
    // because it is responsible for tickling the WDT.
    pthread_attr_init (&attr);              
    spar.sched_priority = 1;
    pthread_attr_setschedparam(&attr, &spar);


    // create the threads
    ret = pthread_create (&button, &attr, (void*) button_thread, NULL);
    if (ret != 0){
    	xil_printf("ERROR (%d) IN MASTER THREAD: could not launch %s\r\n", ret, "button thread");
    	xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
    	return (void*) -3;
    }
    ret = pthread_create (&switches, &attr, (void*) switches_thread, NULL);
    if (ret != 0){
    	xil_printf("ERROR (%d) IN MASTER THREAD: could not launch %s\r\n", ret, "switches thread");
    	xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
    	return (void*) -3;
    }
    ret = pthread_create (&leds, &attr, (void*) leds_thread, NULL);
    if (ret != 0){
    	xil_printf("ERROR (%d) IN MASTER THREAD: could not launch %s\r\n", ret, "switches thread");
    	xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
    	return (void*) -3;
    }

    // initialize the button press semaphore
	ret = sem_init (&btn_press_sema, 0, 0);
    if (ret != 0){
    	xil_printf("ERROR (%d) IN MASTER THREAD: could not initialize %s\r\n", errno, "button press semaphore");
    	xil_printf("FATAL ERROR: Master Thread Terminating\r\n");
    	return (void*) -3;
    }else{
		xil_printf ("MASTER: Button press semaphore has been initialized\n\r");
	}

    // Get the message id for the message queue
    led_msg_id = msgget(led_msg_key,IPC_CREAT);
	
	// Register the interrupt handlers
    ret = register_int_handler(WDT_INTR_NUM, (void*) wdt_handler, NULL);
    if (ret != XST_SUCCESS){
    	return (void*) -4;
    }
    ret = register_int_handler(XPAR_XPS_INTC_0_PUSH_BUTTONS_4BITS_IP2INTC_IRPT_INTR, (void*) button_handler, &BTNInst);
    if (ret != XST_SUCCESS){
    	return (void*) -4;
    }

    // Enable interrupts and start the WDT
    enable_interrupt(XPAR_XPS_INTC_0_PUSH_BUTTONS_4BITS_IP2INTC_IRPT_INTR);
	enable_interrupt(WDT_INTR_NUM);
	xil_printf("MASTER: Interrupts have been enabled\r\n");
	
	XWdtTb_Start(&WDTInst);
	xil_printf("MASTER: Watchdog timer has been started\r\n");

	// set the crash flag to false state
	force_crash = FALSE;
	// master thread main loop
	while(1){
		// let the master sleep and other threads complete their task
		sleep(500);
		// check if the MSB button is ON, If yes then enable force crash of the system
		if(sw_state & mask_all_but_force_crash){
					force_crash = TRUE;
					if(but_mod != force_crash){
					xil_printf("!!!!!!!!!!!!Force Crash Sequence Initialized..........!!!!!!!!!!\r\n");
					but_mod = force_crash;
					}
		}
	}
	
	return NULL;
}

// The button thread
void* button_thread(void *arg){
	//This thread will wait for semaphore till its released and then write to message queue
	while(1){
		sem_wait(&btn_press_sema);
		led_message_ptr->msg_src = BUTTON;
		led_message_ptr->msg_value = btn_state;
		// check if there was successful write to message queue
		if(msgsnd(led_msg_id,led_message_ptr,sizeof(t_LED_message),1)>0)
			xil_printf("button message sending error");
	}
	return NULL;
}

// The switches thread
void* switches_thread(void *arg){
	// This thread continously writes switch condidtions to message queue
	while(1){
		sw_state = XGpio_DiscreteRead(&SWInst,1);
		led_message_ptr->msg_src = SLIDE;
		led_message_ptr->msg_value = sw_state;
		if(msgsnd(led_msg_id,led_message_ptr,sizeof(t_LED_message),1)>0)
			xil_printf("slide switch message sending error");
		// sleep for small time for other threads to start
		sleep(20);
		}
	return NULL;
}

// The leds thread
void* leds_thread(void *arg){
		u8 led_status = 0;
		static u8 button = 0,sw = 0;
		// check message queue for new message and write it to LEDs
		while(1){
			msgrcv(led_msg_id,receive_msg_ptr,sizeof(t_LED_message),0,0);
			if(receive_msg_ptr->msg_src == BUTTON){
				button = (receive_msg_ptr->msg_value<<4);
			}
			else{
				sw = (receive_msg_ptr->msg_value & 0b00001111);
			}
			led_status = (button | sw);
			XGpio_DiscreteWrite(&LEDInst,1,led_status);
			// sleep for some time for other threads to start
			sleep(20);
		}
	return NULL;
}

// init_peripherals() - Initializes the peripherals
XStatus init_peripherals(void){
	// Clean and initialize the platform
	cleanup_platform();
	init_platform();
	XStatus Status;
	// Gpio initializes for push button, Slide switches and LEDs
	Status = XGpio_Initialize(&BTNInst, BTN_GPIO_DEVICEID);
		if (Status != XST_SUCCESS){
			return XST_FAILURE;
		}
		XGpio_SetDataDirection(&BTNInst, 1, 0x00000000);
		// enable gloabal enterrupt on push buttton
		XGpio_InterruptGlobalEnable(&BTNInst);
		XGpio_InterruptEnable(&BTNInst,XPAR_PUSH_BUTTONS_4BITS_IP2INTC_IRPT_MASK);

		Status = XGpio_Initialize(&SWInst, SW_GPIO_DEVICEID);
		if (Status != XST_SUCCESS){
			return XST_FAILURE;
		}
		XGpio_SetDataDirection(&SWInst, 1, 0x00000000);
		Status = XGpio_Initialize(&LEDInst, LED_GPIO_DEVICEID);
		if (Status != XST_SUCCESS){
			return XST_FAILURE;
		}
		XGpio_SetDataDirection(&LEDInst, 1, 0xFFFFFFFF);
	//initialize the WTD
	XWdtTb_Initialize(&WDTInst, WDT_DEVICEID);

	return XST_SUCCESS;
}

// button press interrupt handler
void button_handler(void){
	// Clear the gpio interrupt to avoid it from triggering again
	XGpio_InterruptClear(&BTNInst,XPAR_PUSH_BUTTONS_4BITS_IP2INTC_IRPT_MASK);
	// read the push button condition
	btn_state = XGpio_DiscreteRead(&BTNInst,1);
	// release the semaphore
	sem_post(&btn_press_sema);

	acknowledge_interrupt(BTN_GPIO_INTR_NUM);
}

// WDT interrupt handler
void wdt_handler(void){
	// check for force crash flag to be false and restart the WDT

	if(!force_crash){
		XWdtTb_RestartWdt(&WDTInst);
		xil_printf("WDT : WDT Timer Restarted\r\n");
	}
	// if force crash flag is true, the WDT time will not be restarted causing the system to restart
	acknowledge_interrupt(WDT_INTR_NUM);
}


